docs: add MetaMask integration guide with USDC token setup#100
docs: add MetaMask integration guide with USDC token setup#100zkasuran wants to merge 5 commits into
Conversation
Adds comprehensive guide for integrating Arc Testnet with MetaMask, including the critical wallet_watchAsset call to register USDC. Without this step, users see no USDC balance in MetaMask after receiving tokens, causing confusion and making DApps appear broken. Includes: - Complete onboarding flow with wallet_addEthereumChain - wallet_watchAsset call for USDC token registration - Contract addresses and chain configuration - Why this matters section explaining the impact Fixes circlefin#97 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Great guide — this addresses the exact pain point in #97. One more MetaMask behaviour on Arc Testnet that's worth documenting alongside the Even after the network has been added, calling // ❌ Unreliable on Arc Testnet — may resolve without actually switching
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x4CEF52' }],
});
// ✅ Reliable — works whether network is already added or not
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x4CEF52',
chainName: 'Arc Testnet',
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrls: ['https://rpc.drpc.testnet.arc.network'],
blockExplorerUrls: ['https://testnet.arcscan.app'],
}],
});Note the Also: the |
Three corrections to the MetaMask guide, all verified against the repo config, the MetaMask spec and the live testnet RPC: - chainId was 0x4CF252 (5042002 transposed to 5042770). The testnet chain id is 5042002, which is 0x4CEF52 (hardhat.config.ts, defaults.rs). - nativeCurrency was USDC/6 decimals. MetaMask only supports 18-decimal native currencies and rejects decimals != 18, so the add call has to use ETH/18 even though gas is paid in USDC (issue circlefin#95). The USDC ERC-20 watchAsset call keeps decimals: 6, which is correct. - rpcUrls was https://rpc.arc.network, which does not resolve. Switched to https://rpc.drpc.testnet.arc.network, which returns chain id 0x4cef52 and Access-Control-Allow-Origin: * for browser DApps (circlefin#90). Also documents that wallet_switchEthereumChain fails silently or throws 4902 on Arc Testnet, and that wallet_addEthereumChain should be used as both add and switch (issue circlefin#89).
|
Thanks @osr21, this was a good catch and it surfaced more than the switch-chain issue. I verified everything against the repo config, the MetaMask spec and the live testnet RPC, and pushed a fix. Three corrections plus your switch-chain note:
AI disclosure: these fixes were prepared with help from Claude (Anthropic). I verified each value against the repo, the MetaMask |
|
Thanks for the thorough fixes — the chainId, RPC, and nativeCurrency corrections all match what we see in a working implementation. Two small additions:
// ✅ Correct — await the chain switch first
await window.ethereum.request({ method: 'wallet_addEthereumChain', params: [arcConfig] });
await window.ethereum.request({ method: 'wallet_watchAsset', params: [usdcConfig] });Dead block explorer URLs — worth a note in the guide that |
|
Thanks @osr21, added both, pushed in b33caaa.
AI disclosure: these doc edits were prepared with help from Claude (Anthropic). I verified the three explorer hosts directly (DNS and HTTP) before pushing; the |
|
The ordering and explorer fixes look correct — One more edge case worth documenting here, since it bites right at this exact point in the onboarding flow: even after We hit this in the Arc Relay Bridge on a Base Sepolia → Arc Testnet CCTP bridge:
The message itself was correct (decoded: source=6, dest=26). Only the submission chain was wrong. Root cause: MetaMask updates its Fix: Verify via // After wallet_addEthereumChain resolves, verify the provider transport has caught up
async function waitForProviderChain(expectedChainId: number, retries = 6): Promise<void> {
for (let i = 0; i < retries; i++) {
if (i > 0) await new Promise(r => setTimeout(r, 500 * i));
const provider = new ethers.BrowserProvider(window.ethereum);
const network = await provider.getNetwork();
if (Number(network.chainId) === expectedChainId) return;
}
throw new Error('Network did not stabilise — please switch manually and retry.');
}
// Usage
await window.ethereum.request({ method: 'wallet_addEthereumChain', params: [arcConfig] });
await waitForProviderChain(5042002); // ← this is the missing step
await window.ethereum.request({ method: 'wallet_watchAsset', params: [usdcConfig] });
// now safe to send transactionsA note in the guide's "send a transaction" section (or even a callout in the network setup section) would save a lot of head-scratching. Happy to draft a short addition if helpful. |
|
Thanks @osr21, that race is nasty and exactly the kind of thing this guide should cover. Added in b879beb: a section on the eth_chainId vs routing gap linking #130, your waitForProviderChain approach (with a note that each attempt needs a fresh BrowserProvider since ethers caches the network per instance) and a pointer from the onboarding flow for flows that go straight into a transaction. |
|
Good addition in b879beb — the note about fresh To confirm: our implementation in the Arc Relay Bridge already follows this pattern. Our // getProvider() — called inside each retry attempt
export async function getProvider(): Promise<ethers.BrowserProvider> {
if (!window.ethereum) throw new Error("No wallet detected.");
return new ethers.BrowserProvider(window.ethereum); // always a fresh instance
}
async function getSignerOnChain(expectedChainId: number, maxRetries = 6) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
if (attempt > 0) await new Promise(r => setTimeout(r, 500 * attempt));
const provider = await getProvider(); // ← fresh BrowserProvider every time
const signer = await provider.getSigner();
const network = await provider.getNetwork(); // reads live from RPC, not cache
if (Number(network.chainId) === expectedChainId) return signer;
}
throw new Error("Wallet did not stabilise on expected network.");
}Worth spelling out for anyone adapting this: in ethers v6, If the guide's code example makes that explicit (even just a comment like |
|
im working on building various dapps on Arc Testnet, will update on any patches or comprehensive guides |
Summary
Adds comprehensive MetaMask integration guide for Arc Testnet, including the critical
wallet_watchAssetcall to register USDC.Problem
Issue #97 identified that there's no documented way to add Arc Testnet USDC to MetaMask's token list. Without this:
Solution
New guide at
docs/metamask-integration.mdcovering:wallet_addEthereumChainwallet_watchAsset(the missing piece)Changes
docs/metamask-integration.mdREADME.mdto link to new guideTesting
Code examples follow MetaMask's official API documentation:
Impact
Every DApp on Arc Testnet that involves USDC transfers benefits from this documentation.
Fixes #97